using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Net;
using System.IO;
using System.Xml;

namespace Daenet.Syndication
{
    public class SyndicationFeed
    {
        #region Member Fields

        private List<SyndicationItem> m_Items = new List<SyndicationItem>();

        private Collection<SyndicationPerson> m_Authors = new Collection<SyndicationPerson>();

        private Collection<SyndicationCategory> m_Categories = new Collection<SyndicationCategory>();

        private Collection<SyndicationPerson> m_Contributors = new Collection<SyndicationPerson>();

        private string m_Copyright;

        private string m_Description;

        private string m_Generator;

        private string m_Id;

        private Uri m_ImageUrl;

        private string m_Language;

        private DateTime m_LastUpdatedTime;

        private Collection<SyndicationLink> m_Links = new Collection<SyndicationLink>();

        private string m_Title;

        private SyndicationVersion m_Version;

        #endregion

        #region Member Properties

        /// <summary>
        /// List of the feed entrie(s) (Atom 1.0) or rss item(s) (RSS 2.0)
        /// </summary>
        public List<SyndicationItem> Items
        {
            get { return m_Items; }
        }

        /// <summary>
        /// Author(s) of the feed (Atom 1.0) or rss (RSS 2.0)
        /// </summary>
        public Collection<SyndicationPerson> Authors
        {
            get { return m_Authors; }
            set { m_Authors = value; }
        }

        /// <summary>
        /// Category(s) of the feed (Atom 1.0) or rss (RSS 2.0)
        /// </summary>
        public Collection<SyndicationCategory> Categories
        {
            get { return m_Categories; }
            set { m_Categories = value; }
        }

        /// <summary>
        /// Contributor(s) of the feed (Atom 1.0) or rss (RSS 2.0)
        /// </summary>
        public Collection<SyndicationPerson> Contributors
        {
            get { return m_Contributors; }
            set { m_Contributors = value; }
        }

        /// <summary>
        /// Copyright text of the feed (Atom 1.0) or rss (RSS 2.0)
        /// </summary>
        public string Copyright
        {
            get { return m_Copyright; }

            set { m_Copyright = value; }
        }

        /// <summary>
        /// Description text of the feed (Atom 1.0) or rss (RSS 2.0)
        /// </summary>
        public string Description
        {
            get { return m_Description; }
            set { m_Description = value; }
        }

        /// <summary>
        /// Generator that was used creating the XML
        /// </summary>
        public string Generator
        {
            get { return m_Generator; }
            set { m_Generator = value; }
        }

        /// <summary>
        /// Id of the of the feed (Atom 1.0) or Guid of the rss (RSS 2.0)
        /// </summary>
        public string Id
        {
            get { return m_Id; }
            set { m_Id = value; }
        }

        /// <summary>
        /// Uri of the image (if any) of the feed
        /// </summary>
        public Uri ImageUrl
        {
            get { return m_ImageUrl; }
            set { m_ImageUrl = value; }
        }

        /// <summary>
        /// Language of the feed
        /// </summary>
        public string Language
        {
            get { return m_Language; }
            set { m_Language = value; }
        }

        /// <summary>
        /// Last update or build time of the feed
        /// </summary>
        public DateTime LastUpdatedTime
        {
            get { return m_LastUpdatedTime; }
            set { m_LastUpdatedTime = value; }
        }

        /// <summary>
        /// List of link(s) (alternate, related, self...etc)
        /// </summary>
        public Collection<SyndicationLink> Links
        {
            get { return m_Links; }
            set { m_Links = value; }
        }

        /// <summary>
        /// Title of the feed
        /// </summary>
        public string Title
        {
            get { return m_Title; }
            set { m_Title = value; }
        }

        #endregion

        #region Member Methods
        /// <summary>
        /// Retrieves the XML data from the given Uri and tries to parse
        /// the feed creating the SyndicationFeed object instance.
        /// </summary>
        /// <param name="uri"></param>
        /// <returns></returns>
        /// <seealso cref="SyndicationItem.TryParse"/>
        public static SyndicationFeed Load(Uri uri)
        {
            SyndicationFeed feed = new SyndicationFeed();

            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(uri);
            req.Method = "GET";
            req.Proxy.Credentials = CredentialCache.DefaultCredentials;

            WebResponse resp = req.GetResponse();

            using (Stream stream = resp.GetResponseStream())
            {
                XmlReader reader = XmlReader.Create(stream);
                while (reader.Read())
                {
                    #region Current Version - Atom 1.0
                    if (reader.Name == "feed")
                    {
                        if (reader.NodeType == XmlNodeType.Element)
                        {
                            //version = SyndicationVersion.Atom10;
                            feed = TryParse(reader, SyndicationVersion.Atom10);
                        } 
                        else
                            break;
                    }
                    #endregion

                    #region Current Version - RSS 2.0
                    else if (reader.Name == "rss")
                    {
                        if (reader.NodeType == XmlNodeType.Element)
                        {
                            //version = SyndicationVersion.RSS20;
                            feed = TryParse(reader, SyndicationVersion.RSS20);
                        }
                        else
                            break;
                    }
                    #endregion
                }
            }

            return feed;
        }

        /// <summary>
        /// Retrieves the XML data from the given Uri using given Proxy Address
        /// and User Credentionls and tries to parse
        /// the feed creating the SyndicationFeed object instance.
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="proxy"></param>
        /// <param name="userCredential"></param>
        /// /// <returns></returns>
        public static SyndicationFeed Load(Uri uri, Uri proxy, NetworkCredential userCredential)
        {
            SyndicationFeed feed = new SyndicationFeed();
            WebProxy curProxy = new WebProxy(proxy);
            curProxy.Credentials = userCredential;

            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(uri);
            req.Proxy = curProxy;
            req.Method = "GET";
            WebResponse resp = req.GetResponse();

            using (Stream stream = resp.GetResponseStream())
            {
                XmlReader reader = XmlReader.Create(stream);
                while (reader.Read())
                {
                    #region Current Version - Atom 1.0
                    if (reader.Name == "feed")
                    {
                        if (reader.NodeType == XmlNodeType.Element)
                        {
                            feed = TryParse(reader, SyndicationVersion.Atom10);
                        }
                        else
                            break;
                    }
                    #endregion

                    #region Current Version - RSS 2.0
                    else if (reader.Name == "rss")
                    {
                        if (reader.NodeType == XmlNodeType.Element)
                        {
                            feed = TryParse(reader, SyndicationVersion.RSS20);
                        }
                        else
                            break;
                    }
                    #endregion
                }
            }

            return feed;
        }

        public static SyndicationFeed TryParse(XmlReader reader, SyndicationVersion version)
        {
            string nodeName = reader.Name;

            SyndicationFeed feed = new SyndicationFeed();
            feed.m_Version = version;
            
            while (true)
            {
                reader.Read();

                if (reader.Name.Equals(nodeName) && reader.NodeType == XmlNodeType.EndElement) break;

                if (reader.NodeType == XmlNodeType.Element)
                {
                    #region Atom 1.0
                    if (version == SyndicationVersion.Atom10)
                    {
                        switch (reader.Name)
                        {
                            case "author":
                                feed.Authors.Add(SyndicationPerson.TryParse(reader));
                                break;
                            case "category":
                                SyndicationCategory category = new SyndicationCategory();
                                category.Name = reader.GetAttribute("term") ?? string.Empty;
                                category.Scheme = reader.GetAttribute("scheme") ?? string.Empty;
                                feed.Categories.Add(category);
                                break;
                            case "generator":
                                reader.Read();
                                feed.Generator = reader.Value;
                                break;
                            case "icon":
                                reader.Read();
                                string url = reader.Value;
                                feed.ImageUrl = (!String.IsNullOrEmpty(url) && feed.ImageUrl == null) ? (new Uri(url, UriKind.RelativeOrAbsolute)) : null;
                                break;
                            case "id":
                                reader.Read();
                                feed.m_Id = reader.Value;
                                break;
                            case "link":
                                feed.Links.Add(SyndicationLink.TryParse(reader));
                                break;
                            case "logo":
                                reader.Read();
                                string url2 = reader.Value;
                                feed.ImageUrl = (!String.IsNullOrEmpty(url2) && feed.ImageUrl == null) ? (new Uri(url2, UriKind.RelativeOrAbsolute)) : null;
                                break;
                            case "rights":
                                reader.Read();
                                feed.Copyright = reader.Value;
                                break;
                            case "title":
                                reader.Read();
                                feed.Title = reader.Value;
                                break;
                            case "updated":
                                reader.Read();
                                feed.LastUpdatedTime = XmlConvert.ToDateTime(reader.Value, XmlDateTimeSerializationMode.RoundtripKind);
                                break;
                            case "entry":
                                feed.Items.Add(SyndicationItem.TryParse(reader, SyndicationVersion.Atom10));
                                break;
                        }
                    }
                    #endregion

                    #region RSS 2.0
                    else if (version == SyndicationVersion.RSS20)
                    {
                        switch (reader.LocalName)
                        {
                            case "title":
                                reader.Read();
                                feed.Title = reader.Value;
                                break;
                            case "link":
                                reader.Read();
                                feed.Links.Add(new SyndicationLink(new Uri(reader.Value, UriKind.RelativeOrAbsolute)));
                                break;
                            case "description":
                                reader.Read();
                                feed.Description = reader.Value;
                                break;
                            case "language":
                                reader.Read();
                                feed.Language = reader.Value;
                                break;
                            case "pubDate":
                                // Read Through
                                reader.Read();
                                break;
                            case "lastBuildDate":
                                reader.Read();
                                DateTime updated;
                                try
                                {
                                    updated = Convert.ToDateTime(reader.Value);
                                }
                                catch (FormatException)
                                {
                                    updated = DateTime.MinValue;
                                }
                                feed.m_LastUpdatedTime = updated;
                                break;
                            case "generator":
                                reader.Read();
                                feed.Generator = reader.Value;
                                break;
                            case "copyright":
                                reader.Read();
                                feed.Copyright = reader.Value;
                                break;
                            case "category":
                                reader.Read();
                                feed.Categories.Add(new SyndicationCategory(reader.Value, reader.Value));
                                break;
                            case "image":
                                // TODO:
                                reader.Read();
                                feed.ImageUrl = new Uri(reader.Value, UriKind.RelativeOrAbsolute);
                                break;
                            case "item":
                                feed.Items.Add(SyndicationItem.TryParse(reader, SyndicationVersion.RSS20));
                                break;
                        }
                    }
                    #endregion 
                }
            }

            return feed;
        }

        #endregion
    }
}
